<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta
			name="viewport"
			content="width=device-width, initial-scale=1.0, user-scalable=yes"
		/>
		<title>WordPress PHP Playground</title>
		<style>
			html,
			body {
				margin: 0;
				height: 100%;
				display: flex;
				flex-direction: column;
				font-family: system-ui, sans-serif;
			}

			#app {
				flex: 1;
				display: flex;
				min-height: 0;
			}

			/* Editor (left) */
			#editorPane {
				flex: 1;
				display: flex;
				flex-direction: column;
				min-width: 280px;
			}

			.controls {
				padding: 8px 12px;
				background: #f5f5f5;
				border-bottom: 1px solid #ddd;
				display: flex;
				align-items: center;
				justify-content: space-between;
				gap: 8px;
				&,
				* {
					font-size: 16px;
				}
			}

			.controls h1 {
				margin: 0;
				font-size: 18px;
				font-weight: 600;
				color: #333;
			}

			.controls-right {
				display: flex;
				align-items: center;
				gap: 8px;
			}

			.controls-right button {
				padding: 6px 12px;
				border: 1px solid #ccc;
				border-radius: 4px;
				background: white;
				cursor: pointer;
				transition: background-color 0.2s;
			}

			.controls-right button:hover {
				background: #f0f0f0;
			}

			.controls-right select {
				padding: 6px 8px;
				border: 1px solid #ccc;
				border-radius: 4px;
				background: white;
			}

			#helpBtn {
				color: #0073aa;
				text-decoration: none;
				font-size: 14px;
				margin-left: 8px;
			}

			#helpBtn:hover {
				text-decoration: underline;
			}

			#editor {
				flex: 1;
				font-size: 14px;
			}

			/* CodeMirror font size */
			#editor .cm-editor {
				font-size: 14px;
				line-height: 1.4;
			}

			#editor .cm-content {
				font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono',
					Consolas, 'Courier New', monospace;
			}

			/* Enhanced PHP syntax highlighting */
			#editor .cm-editor .tok-keyword {
				color: #0969da;
				font-weight: 600;
			}
			#editor .cm-editor .tok-string {
				color: #032f62;
			}
			#editor .cm-editor .tok-comment {
				color: #6a737d;
				font-style: italic;
			}
			#editor .cm-editor .tok-variableName {
				color: #d73a49;
			}
			#editor .cm-editor .tok-number {
				color: #005cc5;
			}
			#editor .cm-editor .tok-operator {
				color: #d73a49;
			}
			#editor .cm-editor .tok-punctuation {
				color: #24292e;
			}
			#editor .cm-editor .tok-typeName {
				color: #6f42c1;
			}
			#editor .cm-editor .tok-propertyName {
				color: #005cc5;
			}
			#editor .cm-editor .tok-function {
				color: #6f42c1;
				font-weight: 600;
			}

			/* Preview (right) */
			#previewPane {
				flex: 1;
				position: relative;
				min-width: 320px;
			}

			#preview {
				position: absolute;
				inset: 0;
				width: 100%;
				height: 100%;
				border: 0;
			}

			/* Modal styles */
			.modal {
				display: none;
				position: fixed;
				z-index: 1000;
				left: 0;
				top: 0;
				width: 100%;
				height: 100%;
				background-color: rgba(0, 0, 0, 0.5);
			}

			.modal.show {
				display: flex;
				align-items: center;
				justify-content: center;
			}

			.modal-content {
				background: white;
				border-radius: 8px;
				padding: 24px;
				max-width: 600px;
				max-height: 80vh;
				overflow-y: auto;
				position: relative;
				margin: 20px;
				box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
			}

			.modal-header {
				display: flex;
				justify-content: space-between;
				align-items: center;
				margin-bottom: 16px;
				padding-bottom: 16px;
				border-bottom: 1px solid #ddd;
			}

			.modal-header h2 {
				margin: 0;
				color: #333;
				font-size: 20px;
			}

			.close {
				background: none;
				border: none;
				font-size: 24px;
				cursor: pointer;
				color: #666;
				padding: 0;
				width: 30px;
				height: 30px;
				display: flex;
				align-items: center;
				justify-content: center;
			}

			.close:hover {
				color: #333;
			}

			.modal-body {
				line-height: 1.6;
				color: #555;
			}

			.modal-body h3 {
				color: #333;
				margin-top: 20px;
				margin-bottom: 8px;
			}

			.modal-body ul {
				padding-left: 20px;
			}

			.modal-body a {
				color: #0073aa;
				text-decoration: none;
			}

			.modal-body a:hover {
				text-decoration: underline;
			}

			.modal-body code {
				background: #f5f5f5;
				padding: 2px 4px;
				border-radius: 3px;
				font-family: 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono',
					Consolas, 'Courier New', monospace;
				font-size: 14px;
			}

			/* Responsive layout: stack panes on small screens */
			@media (max-width: 1200px) {
				#app {
					flex-direction: column;
				}
				#previewPane {
					height: 45vh;
					min-width: 0;
				}
				#editorPane {
					min-width: 0;
				}
			}

			/* Tablet and small desktop adjustments */
			@media (max-width: 900px) {
				.controls h1 {
					font-size: 16px;
				}
				.controls-right {
					gap: 6px;
				}
				.controls-right label,
				.controls-right select,
				.controls-right button,
				.controls-right a {
					font-size: 14px;
				}
				#previewPane {
					height: 40vh;
				}
			}

			/* Mobile phone adjustments */
			@media (max-width: 768px) {
				.controls {
					flex-direction: column;
					align-items: stretch;
					gap: 8px;
					padding: 12px;
				}
				.controls h1 {
					font-size: 14px;
					text-align: center;
					margin-bottom: 4px;
				}
				.controls-right {
					justify-content: center;
					flex-wrap: wrap;
					gap: 8px;
				}
				.controls-right button {
					padding: 8px 12px;
					min-height: 44px; /* Touch-friendly */
				}
				.controls-right select {
					min-height: 44px; /* Touch-friendly */
				}
				#previewPane {
					height: 35vh;
				}
				#editor .cm-editor {
					font-size: 13px;
				}
			}

			/* Small mobile adjustments */
			@media (max-width: 480px) {
				.controls {
					padding: 8px;
				}
				.controls h1 {
					font-size: 13px;
				}
				.controls-right {
					font-size: 13px;
				}
				.controls-right button,
				.controls-right select {
					min-height: 40px;
					font-size: 13px;
				}
				#helpBtn {
					font-size: 12px;
				}
				#previewPane {
					height: 30vh;
				}
				#editor .cm-editor {
					font-size: 12px;
					line-height: 1.3;
				}
			}

			/* Modal responsive styles */
			@media (max-width: 768px) {
				.modal-content {
					margin: 5px;
					max-width: calc(100% - 10px);
					max-height: calc(100vh - 10px);
					border-radius: 6px;
					padding: 16px;
				}
				.modal-header h2 {
					font-size: 18px;
				}
				.modal-body {
					font-size: 14px;
				}
				.modal-body h3 {
					font-size: 16px;
				}
				.modal-body h4 {
					font-size: 15px;
				}
				.modal-body pre {
					font-size: 11px !important;
					overflow-x: auto;
					white-space: pre-wrap;
					word-break: break-all;
				}
			}

			/* Very small screens */
			@media (max-width: 360px) {
				.controls-right {
					flex-direction: column;
					align-items: center;
				}
				.controls-right button,
				.controls-right select {
					width: 100%;
					max-width: 200px;
				}
			}

			/* Landscape phone orientation */
			@media (max-height: 500px) and (orientation: landscape) {
				#app {
					flex-direction: row;
				}
				#previewPane {
					height: auto;
					width: 50%;
				}
				.controls {
					padding: 4px 8px;
				}
				.controls h1 {
					font-size: 12px;
				}
				.controls-right {
					gap: 4px;
				}
			}
		</style>
	</head>
	<body>
		<div id="app">
			<div id="editorPane">
				<div class="controls">
					<h1>WordPress PHP Playground</h1>
					<div class="controls-right">
						<label for="wpVersion">WordPress</label>
						<select id="wpVersion">
							<option>Loading...</option>
						</select>
						<label for="phpVersion">PHP</label>
						<select id="phpVersion">
							<option value="8.4" selected>8.4</option>
							<option value="8.3">8.3</option>
							<option value="8.2">8.2</option>
							<option value="8.1">8.1</option>
							<option value="8.0">8.0</option>
							<option value="7.4">7.4</option>
							<option value="7.3">7.3</option>
							<option value="7.2">7.2</option>
						</select>
						<button id="runBtn">Run</button>
						<a href="#" id="helpBtn">How does this work?</a>
					</div>
				</div>
				<div id="editor"></div>
			</div>
			<div id="previewPane">
				<iframe id="preview" title="WordPress Playground"></iframe>
			</div>
		</div>

		<!-- Help Modal -->
		<div id="helpModal" class="modal">
			<div class="modal-content">
				<div class="modal-header">
					<h2>✨ WordPress PHP Playground ✨</h2>
					<button class="close" id="closeModal">&times;</button>
				</div>
				<div class="modal-body">
					<p>
						This app runs PHP code directly inside your browser via
						<a href="https://wordpress.org/playground/"
							>WordPress Playground</a
						>.
					</p>

					<p><strong>Here's how:</strong></p>

					<ul>
						<li>
							<strong>🚀 WebAssembly Binary:</strong> The entire
							PHP runtime is compiled with plenty of extensions to
							run natively in your browser
						</li>
						<li>
							<strong>🔌 JavaScript syscall handlers:</strong>
							Network requests, subprocesses, file locks, and
							other system calls are supported via JavaScript
							handlers.
						</li>
						<li>
							<strong>🔗 Shareable URLs:</strong> Code and
							settings are encoded in the URL fragment, copy,
							paste, and enjoy it together!
						</li>
					</ul>

					<h2>💡 Tips</h2>
					<ul>
						<li>
							<strong>🔗 Embed in your app:</strong> You can embed
							this playground in an iframe to provide interactive
							PHP code snippets. Provide your base64-encoded code
							snippet, PHP version, and WordPress version in the
							URL fragment:
							<pre
								style="
									background: #f5f5f5;
									padding: 8px;
									margin: 8px 0;
									border-radius: 4px;
									font-size: 13px;
									overflow-x: auto;
								"
							><code>&lt;iframe 
  src="https://playground.wordpress.net/php-playground.html#eyJjb2RlIjoiPD9waHBcblxuZWNobyBcIkkgYW0gYSBjb2RlIHNuaXBwZXQhXCI7XG4iLCJwaHAiOiI4LjQifQ=="
  width="100%" 
  height="600"&gt;
&lt;/iframe&gt;</code></pre>
							The above example loads:
							<code>&lt;?php echo "I am a code snippet!"; </code>
						</li>
					</ul>

					<p>
						<strong
							><a
								href="https://wordpress.org/playground/"
								target="_blank"
								>Learn more about WordPress Playground!</a
							></strong
						>
					</p>
				</div>
			</div>
		</div>

		<script>
			// Function to restore code, PHP version, and WordPress version from URL fragment
			function loadStateFromURL() {
				const fragment = window.location.hash.slice(1); // Remove #
				if (fragment) {
					const decoded = decodeBase64UTF8(fragment);
					if (decoded !== null) {
						try {
							const state = JSON.parse(decoded);
							return {
								code: state.code || null,
								phpVersion: state.php || null,
								wpVersion: state.wp || null,
							};
						} catch (e) {
							// Try legacy format (just code)
							return {
								code: decoded,
								phpVersion: null,
								wpVersion: null,
							};
						}
					}
				}
				return { code: null, phpVersion: null, wpVersion: null };
			}

			/**
			 * URL Fragment State Storage
			 *
			 * This implementation stores and restores PHP code, PHP version, and WordPress version from the URL fragment using base64 encoding.
			 * Features:
			 * - UTF-8 safe encoding/decoding
			 * - Stores code, PHP version, and WordPress version
			 * - Automatic saving on code changes (debounced to 500ms)
			 * - Automatic saving when PHP or WordPress version changes
			 * - Immediate saving when running code (Cmd+S)
			 * - Browser navigation support (back/forward buttons)
			 * - Shareable URLs with embedded code and versions
			 * - Backward compatibility with legacy code-only URLs
			 */

			// Utility functions for UTF-8 safe base64 encoding/decoding
			function encodeBase64UTF8(str) {
				// Convert string to UTF-8 bytes, then to base64
				return btoa(
					encodeURIComponent(str).replace(
						/%([0-9A-F]{2})/g,
						(match, p1) => {
							return String.fromCharCode('0x' + p1);
						}
					)
				);
			}

			function decodeBase64UTF8(base64) {
				try {
					// Decode base64 to bytes, then interpret as UTF-8
					return decodeURIComponent(
						atob(base64)
							.split('')
							.map((c) => {
								return (
									'%' +
									('00' + c.charCodeAt(0).toString(16)).slice(
										-2
									)
								);
							})
							.join('')
					);
				} catch (e) {
					console.warn(
						'Failed to decode base64 from URL fragment:',
						e
					);
					return null;
				}
			}

			// Function to save code, PHP version, and WordPress version to URL fragment
			function saveStateToURL(code, phpVersion, wpVersion) {
				const state = JSON.stringify({
					code: code,
					php: phpVersion,
					wp: wpVersion,
				});
				const encoded = encodeBase64UTF8(state);
				// Use replaceState to avoid adding to browser history
				window.history.replaceState(null, null, '#' + encoded);
			}

			var phpVersionSelect = document.getElementById('phpVersion');
			var wpVersionSelect = document.getElementById('wpVersion');

			function restoreVersionsFromURL() {
				var defaultPhpVersion = '8.4';
				var defaultWpVersion = '6.8'; // Will be updated with actual latest version
				const {
					phpVersion: initialPhpVersion,
					wpVersion: initialWpVersion,
				} = loadStateFromURL();
				phpVersionSelect.value = initialPhpVersion || defaultPhpVersion;
				if (initialWpVersion && wpVersionSelect.options.length > 1) {
					wpVersionSelect.value = initialWpVersion;
				}
			}

			// Modal functionality
			const helpBtn = document.getElementById('helpBtn');
			const helpModal = document.getElementById('helpModal');
			const closeModal = document.getElementById('closeModal');

			function openModal() {
				helpModal.classList.add('show');
				document.body.style.overflow = 'hidden';
			}

			function closeModalFn() {
				helpModal.classList.remove('show');
				document.body.style.overflow = '';
			}

			helpBtn.addEventListener('click', (e) => {
				e.preventDefault();
				openModal();
			});

			closeModal.addEventListener('click', closeModalFn);

			// Close modal when clicking outside
			helpModal.addEventListener('click', (e) => {
				if (e.target === helpModal) {
					closeModalFn();
				}
			});

			// Close modal with Escape key
			document.addEventListener('keydown', (e) => {
				if (
					e.key === 'Escape' &&
					helpModal.classList.contains('show')
				) {
					closeModalFn();
				}
			});
		</script>

		<script type="module">
			import {
				EditorState,
				Compartment,
			} from 'https://esm.sh/@codemirror/state';
			import {
				EditorView,
				keymap,
				lineNumbers,
				highlightActiveLine,
				highlightActiveLineGutter,
				dropCursor,
				rectangularSelection,
				crosshairCursor,
			} from 'https://esm.sh/@codemirror/view';
			import {
				defaultKeymap,
				history,
				historyKeymap,
				indentWithTab,
			} from 'https://esm.sh/@codemirror/commands';
			import {
				searchKeymap,
				highlightSelectionMatches,
			} from 'https://esm.sh/@codemirror/search';
			import {
				autocompletion,
				completionKeymap,
				closeBrackets,
				closeBracketsKeymap,
			} from 'https://esm.sh/@codemirror/autocomplete';
			import {
				foldGutter,
				indentOnInput,
				bracketMatching,
				foldKeymap,
				syntaxHighlighting,
				defaultHighlightStyle,
			} from 'https://esm.sh/@codemirror/language';
			import { php } from 'https://esm.sh/@codemirror/lang-php';
			import {
				startPlaygroundWeb,
				SupportedPHPVersionsList,
			} from 'https://playground.wordpress.net/client/index.js';

			const editorEl = document.getElementById('editor');
			const phpCompartment = new Compartment();

			const defaultPhpVersion = '8.4';
			const {
				phpVersion: initialPhpVersion,
				wpVersion: initialWpVersion,
			} = loadStateFromURL();
			const startingPhpVersion = initialPhpVersion || defaultPhpVersion;
			const startingWpVersion = initialWpVersion || 'latest';

			// Populate PHP version select options from SupportedPHPVersionsList
			phpVersionSelect.innerHTML = ''; // Clear existing options
			SupportedPHPVersionsList.forEach((version) => {
				const option = document.createElement('option');
				option.value = version;
				option.textContent = version;
				phpVersionSelect.appendChild(option);
			});

			// WordPress versions will be populated after the client connects

			// Set the versions from URL or defaults
			restoreVersionsFromURL();

			const defaultCode = `<?php
echo "Hello from PHP " . phpversion();
echo "<br>";

// WordPress is available if you need it!
require '/wordpress/wp-load.php';

echo "WordPress " . wp_get_wp_version() . "<br>";

$html_processor = WP_HTML_Processor::create_fragment('<p><span>Hey!</span></p>');
$html_processor->next_tag();
var_dump($html_processor->get_tag());
?>`;

			// Load initial state from URL
			const { code: initialCode } = loadStateFromURL();
			const startingCode = initialCode || defaultCode;

			const state = EditorState.create({
				doc: startingCode,
				extensions: [
					// Basic editor features
					lineNumbers(),
					highlightActiveLineGutter(),
					highlightActiveLine(),
					foldGutter(),
					dropCursor(),
					rectangularSelection(),
					crosshairCursor(),

					// Language and syntax
					phpCompartment.of(php()),
					syntaxHighlighting(defaultHighlightStyle),
					indentOnInput(),
					bracketMatching(),
					closeBrackets(),

					// History and undo/redo
					history(),

					// Search
					highlightSelectionMatches(),

					// Autocompletion
					autocompletion(),

					// Update listener to save code to URL fragment
					EditorView.updateListener.of((update) => {
						if (update.docChanged) {
							const code = update.state.doc.toString();
							const phpVersion = phpVersionSelect.value;
							const wpVersion = wpVersionSelect.value;
							// Debounce URL updates to avoid excessive history updates
							clearTimeout(window.saveCodeTimeout);
							window.saveCodeTimeout = setTimeout(() => {
								saveStateToURL(code, phpVersion, wpVersion);
							}, 500);
						}
					}),

					// Keymaps (order matters - custom keymaps should come first)
					keymap.of([
						{
							key: 'Mod-s',
							preventDefault: true,
							run() {
								executeCode();
								return true;
							},
						},
						...closeBracketsKeymap,
						...completionKeymap,
						...foldKeymap,
						...searchKeymap,
						...historyKeymap,
						...defaultKeymap,
						indentWithTab,
					]),
				],
			});

			const view = new EditorView({ state, parent: editorEl });

			const runBtn = document.getElementById('runBtn');
			const previewIframe = document.getElementById('preview');

			// Set initial versions from URL
			if (startingPhpVersion) {
				phpVersionSelect.value = startingPhpVersion;
			}
			if (startingWpVersion && wpVersionSelect.options.length > 1) {
				wpVersionSelect.value = startingWpVersion;
			}

			let client = null;
			let currentPhpVersion = phpVersionSelect.value;
			let currentWpVersion = wpVersionSelect.value;
			let bootPromise = Promise.resolve();
			let runCounter = 0;

			// Detect platform for keyboard shortcut display
			const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
			const shortcutText = isMac ? 'Cmd+S' : 'Ctrl+S';
			runBtn.textContent = `Run (${shortcutText})`;

			// Save to URL when PHP version changes
			phpVersionSelect.addEventListener('change', () => {
				const code = view.state.doc.toString();
				const phpVersion = phpVersionSelect.value;
				const wpVersion = wpVersionSelect.value;
				saveStateToURL(code, phpVersion, wpVersion);
			});

			// Save to URL when WordPress version changes
			wpVersionSelect.addEventListener('change', () => {
				const code = view.state.doc.toString();
				const phpVersion = phpVersionSelect.value;
				const wpVersion = wpVersionSelect.value;
				saveStateToURL(code, phpVersion, wpVersion);
			});

			async function bootPlayground(phpVersion, wpVersion) {
				// Point the iframe at the appropriate PHP and WordPress versions using the Query API
				previewIframe.src = `https://playground.wordpress.net/remote.html`;
				client = await startPlaygroundWeb({
					iframe: previewIframe,
					remoteUrl: previewIframe.src,
					blueprint: {
						preferredVersions: {
							wp: wpVersion,
							php: phpVersion,
						},
					},
				});

				// Populate WordPress versions from the connected client
				try {
					const { all, latest } =
						await client.getMinifiedWordPressVersions();
					const keys = Object.keys(all);
					wpVersionSelect.innerHTML = '';
					for (const version of keys) {
						const option = document.createElement('option');
						option.value = version;
						option.textContent = version;
						wpVersionSelect.appendChild(option);
					}
					// Use the requested version if available; otherwise fall back to latest
					wpVersionSelect.value = keys.includes(wpVersion)
						? wpVersion
						: latest;
				} catch (e) {
					console.warn(
						'Failed to load WordPress versions list from client',
						e
					);
				}
				await client.isReady;
				await client.writeFile('/wordpress/code.php', startingCode);
				await client.goTo('/code.php'); // Blank document
				// Sync current versions after boot to avoid unnecessary reboots on first run
				currentPhpVersion = phpVersionSelect.value;
				currentWpVersion = wpVersionSelect.value;
			}

			async function executeCode() {
				const code = view.state.doc.toString();
				const phpVersion = phpVersionSelect.value;
				const wpVersion = wpVersionSelect.value;

				// Save code, PHP version, and WordPress version to URL fragment immediately when running
				saveStateToURL(code, phpVersion, wpVersion);

				// Ensure initial boot finished before proceeding
				await bootPromise;

				if (
					phpVersion !== currentPhpVersion ||
					wpVersion !== currentWpVersion
				) {
					currentPhpVersion = phpVersion;
					currentWpVersion = wpVersion;
					bootPromise = bootPlayground(
						currentPhpVersion,
						currentWpVersion
					);
					await bootPromise;
				}
				await client.writeFile('/wordpress/code.php', code);
				runCounter += 1;
				const cacheBuster = 'run=' + runCounter + '-' + Date.now();
				await client.goTo('/code.php?' + cacheBuster);
			}

			runBtn.addEventListener('click', executeCode);

			// Handle browser navigation (back/forward) to restore code from URL
			window.addEventListener('hashchange', () => {
				const {
					code: codeFromURL,
					phpVersion: phpVersionFromURL,
					wpVersion: wpVersionFromURL,
				} = loadStateFromURL();

				if (codeFromURL !== null) {
					const currentCode = view.state.doc.toString();
					if (codeFromURL !== currentCode) {
						// Update editor content
						view.dispatch({
							changes: {
								from: 0,
								to: view.state.doc.length,
								insert: codeFromURL,
							},
						});
					}
				}

				if (
					phpVersionFromURL !== null &&
					phpVersionFromURL !== phpVersionSelect.value
				) {
					// Update PHP version selector
					phpVersionSelect.value = phpVersionFromURL;
				}

				if (
					wpVersionFromURL !== null &&
					wpVersionFromURL !== wpVersionSelect.value
				) {
					// Update WordPress version selector
					wpVersionSelect.value = wpVersionFromURL;
				}
			});

			// Save initial state to URL if not already present
			if (!window.location.hash) {
				saveStateToURL(
					startingCode,
					startingPhpVersion,
					startingWpVersion
				);
			}

			// Initial boot
			bootPromise = bootPlayground(currentPhpVersion, currentWpVersion);
		</script>
	</body>
</html>
